import time
import json
import logging
import re
import os
import asyncio
import urllib.parse
import base64
import requests
from io import BytesIO
from typing import Dict, Any, Optional, List

from app.models.schemas import ProcessingRequest
from app.utils.text_utils import extract_json_from_text, generate_timing_visualization
from app.services.processor_service import create_processor
from app.utils.feishu_spreadsheet import FeishuSpreadsheetUtil, clean_json_string  # 直接导入清理函数

from app.utils.zxy_get import SimpleNotesFetcher
from config.setting import ZXY_API_BASE_URL, ZXY_API_SECRET

from prompts import (
    PROMPT_CREATOR_STYLE_SYSTEM, PROMPT_CREATOR_STYLE_USER,
    PROMPT_FINAL_CONTENT_SYSTEM, PROMPT_FINAL_CONTENT_USER,
    PROMPT_VIDEO_SCRIPT_SYSTEM, PROMPT_VIDEO_SCRIPT_USER
)

from app.core.request_context import get_request_id

from app.utils.logger import get_logger

logger = get_logger(__name__)


class ContentService:
    async def generate_content_strategy(self, data):
        # 调试：在服务开始时检查 request_id
        current_request_id = get_request_id()
        print(f"🔍 [服务调试] generate_content_strategy 开始，当前 request_id: {current_request_id}")

        logger.info("【前置准备】zxy_get数据转换完成，caption长度：693，图片数量：5")
        # ... 其他代码

    async def process_topic(self, data):
        # 调试：检查 request_id
        current_request_id = get_request_id()
        print(f"🔍 [服务调试] process_topic 开始，当前 request_id: {current_request_id}")

        logger.info("【话题分析】开始，ProductHighlights长度：862")


# 图片URL转Base64函数（保留原优化逻辑）
def image_url_to_base64(image_url: str, timeout: int = 15) -> Optional[str]:
    try:
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
            "Accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
        }

        logger.info(f"开始下载图片：{image_url}（超时时间：{timeout}s）")
        response = requests.get(
            image_url,
            timeout=timeout,
            stream=True,
            allow_redirects=True,
            headers=headers
        )
        response.raise_for_status()

        content_type = response.headers.get("Content-Type", "")
        if not content_type.startswith("image/"):
            logger.warning(f"URL不是图片类型（Content-Type：{content_type}）：{image_url}")
            return None

        image_bytes = BytesIO(response.content).read()
        base64_str = base64.b64encode(image_bytes).decode("utf-8")
        logger.info(f"图片转Base64成功：{image_url}（大小：{len(image_bytes)}字节）")
        return f"data:{content_type};base64,{base64_str}"

    except requests.exceptions.HTTPError as e:
        logger.error(f"图片下载失败（HTTP错误）：{str(e)}（URL：{image_url}）")
    except requests.exceptions.Timeout:
        logger.error(f"图片下载超时（超过{timeout}s）：{image_url}")
    except requests.exceptions.ConnectionError:
        logger.error(f"图片连接失败（网络问题）：{image_url}")
    except Exception as e:
        logger.error(f"图片处理异常：{str(e)}（URL：{image_url}）", exc_info=True)
    return None


def extract_user_uuid_from_url(url: str) -> Optional[str]:
    try:
        parsed_url = urllib.parse.urlparse(url)
        path_segments = [seg for seg in parsed_url.path.split('/') if seg.strip()]
        return path_segments[-1] if path_segments else None
    except Exception as e:
        logger.error(f"提取达人ID失败：{str(e)}", exc_info=True)
        return None


def extract_spreadsheet_token_from_url(url: str) -> Optional[str]:
    """从飞书表格URL中提取spreadsheet_token"""
    try:
        parsed_url = urllib.parse.urlparse(url)
        path_segments = [seg for seg in parsed_url.path.split('/') if seg.strip()]

        # 飞书表格URL格式通常是：https://example.feishu.cn/sheets/{spreadsheet_token}
        if 'sheets' in path_segments:
            sheets_index = path_segments.index('sheets')
            if sheets_index + 1 < len(path_segments):
                return path_segments[sheets_index + 1]

        # 如果直接就是token格式（字母数字组合），直接返回最后一个segment
        if path_segments:
            last_segment = path_segments[-1]
            # 简单的token格式验证（飞书token通常是字母数字组合）
            if re.match(r'^[a-zA-Z0-9]+$', last_segment):
                return last_segment

        return None
    except Exception as e:
        logger.error(f"从URL提取spreadsheet_token失败：{str(e)}")
        return None


def extract_direction_from_content(content: Any) -> Optional[str]:
    """保留原逻辑：从final_strategy提取最终方向"""
    if isinstance(content, str):
        try:
            content = json.loads(content)
        except:
            pass

    if isinstance(content, dict):
        direction_fields = ["direction", "content_direction", "主题", "方向"]
        for field in direction_fields:
            if field in content and isinstance(content[field], str):
                return content[field].strip()

        for field in ["summary", "description", "内容摘要"]:
            if field in content and isinstance(content[field], str):
                direction_keywords = ["种草", "测评", "推荐", "对比", "教程", "解析"]
                for keyword in direction_keywords:
                    if keyword in content[field]:
                        return keyword
        return None

    if isinstance(content, list) and len(content) > 0:
        return extract_direction_from_content(content[0])

    return None


async def generate_content_strategy(
        request: ProcessingRequest,
        volcano_client,
        feishu_client
) -> Dict[str, Any]:
    timing_data = {}
    start_time = time.time()

    try:
        # 1. 基础参数校验（保留原逻辑）
        direction_lower = request.direction.lower()
        if "种草" not in direction_lower and "测评" not in direction_lower:
            raise Exception("风格类型必须包含'种草'或'测评'关键词")

        # 2. 从video_outline_link中提取spreadsheet_token
        spreadsheet_token = extract_spreadsheet_token_from_url(request.video_outline_link)
        if not spreadsheet_token:
            raise Exception(f"无法从video_outline_link中提取spreadsheet_token: {request.video_outline_link}")

        logger.info(f"从video_outline_link提取到token: {spreadsheet_token}")

        # 3. 读取请求体核心参数（保留原逻辑，参数名对齐）
        ppt_start = time.time()
        product_highlights = request.ProductHighlights  # 产品亮点（必填）
        if not product_highlights.strip():
            raise Exception("ProductHighlights（产品内容）不能为空")
        timing_data["ppt_extraction"] = time.time() - ppt_start

        outline_start = time.time()
        # 从飞书表格读取视频大纲内容
        video_outline = ""  # 初始化为空字符串
        try:
            # 使用FeishuSpreadsheetUtil读取表格内容
            spreadsheet_util = FeishuSpreadsheetUtil()

            # 读取B8单元格作为视频大纲内容
            outline_content = await spreadsheet_util.read_range(
                spreadsheet_token=spreadsheet_token,  # 使用从video_outline_link提取的token
                range="B8"  # 读取B8单元格
            )

            if outline_content and len(outline_content) > 0 and len(outline_content[0]) > 0:
                video_outline = str(outline_content[0][0])
                logger.info(f"成功从飞书表格B8单元格读取视频大纲，内容长度：{len(video_outline)}")
            else:
                logger.warning("从飞书表格B8单元格读取的视频大纲内容为空")

            # 关闭客户端
            await spreadsheet_util.sheet_manager.close()
        except Exception as e:
            logger.error(f"读取飞书表格视频大纲失败：{str(e)}")
            # 如果读取失败，保持video_outline为空字符串
            video_outline = ""

        outline_advice = request.outline_direction  # 大纲方向建议（新入参，来自请求体）
        timing_data["outline_extraction"] = time.time() - outline_start

        # 4. 达人资源处理（保留原zxy_get逻辑，获取caption和image）
        influencer_start = time.time()
        user_uuid = extract_user_uuid_from_url(request.xhs_link)
        if not user_uuid:
            raise Exception(f"从URL提取达人ID失败：{request.xhs_link}")

        try:
            zxy_fetcher = SimpleNotesFetcher(
                base_url=ZXY_API_BASE_URL,
                secret=ZXY_API_SECRET
            )
            zxy_result = zxy_fetcher.fetch_recent_5_notes(
                user_uuid=user_uuid,
                days=180
            )

            if zxy_result["status"] != "success" or not zxy_result["data"].get("notes"):
                error_msg = zxy_result.get("msg", "未知错误")
                raise Exception(f"zxy_get获取数据失败：{error_msg}")

            notes = zxy_result["data"]["notes"]
            logger.info(f"成功获取{len(notes)}篇笔记，提取达人文字和图片...")

            # 4.1 提取达人文字内容（caption）
            caption_parts = []
            for i, note in enumerate(notes):
                note_text = (
                    f"【笔记{i + 1}】\n"
                    f"标题：{note.get('title', '无标题')}\n"
                    f"简介：{note.get('description', '无简介')}\n"
                    f"内容：{note.get('content', '无内容')}\n"
                    f"发布时间：{note.get('publicTime', '未知')}\n"
                )
                caption_parts.append(note_text)
            caption = "\n\n".join(caption_parts)

            # 4.2 提取达人图片并转Base64
            image_urls = []
            possible_image_fields = ["images", "imagesList", "pic_urls", "image_urls"]
            for i, note in enumerate(notes):
                for field in possible_image_fields:
                    images_str = note.get(field, "")
                    if images_str:
                        urls = [img.strip() for img in images_str.split(",") if img.strip()] if isinstance(images_str,
                                                                                                           str) else \
                            [img.strip() for img in images_str if isinstance(img, str) and img.strip()]
                        if urls:
                            image_urls.extend(urls)
                            logger.info(f"笔记{i + 1}从字段[{field}]提取到{len(urls)}个图片URL")
                            break
                else:
                    logger.warning(f"笔记{i + 1}未提取到图片URL（尝试字段：{possible_image_fields}）")

            # 图片URL去重+转Base64
            unique_image_urls = list(set(image_urls))
            image_base64_list = [img for url in unique_image_urls if (img := image_url_to_base64(url))]
            logger.info(f"图片处理结果：原始URL {len(unique_image_urls)}个 → 转换成功Base64 {len(image_base64_list)}个")

            influencer_data = {
                "caption": caption,
                "image": image_base64_list
            }

        except Exception as e:
            logger.error(f"zxy_get处理异常：{str(e)}", exc_info=True)
            raise Exception(f"zxy_get处理异常：{str(e)}")

        timing_data["influencer_extraction"] = time.time() - influencer_start

        # 5. 核心并行任务：仅保留「达人风格解析」+「final策略生成」（按需求并发）
        parallel_start = time.time()
        # 任务1：达人风格解析（输出expert_style）
        creator_style_task = process_creator_style(
            caption=influencer_data["caption"],
            image=influencer_data["image"],
            direction=request.direction,
            volcano_client=volcano_client
        )
        # 任务2：final策略生成（输入：direction+outline_advice，按需求仅2个入参）
        final_strategy_task = process_final_content(
            direction=request.direction,  # 来自请求体的风格类型
            outline_advice=outline_advice,  # 来自请求体的大纲方向建议
            volcano_client=volcano_client
        )

        # 并发执行2个任务
        creator_style, final_strategy = await asyncio.gather(
            creator_style_task, final_strategy_task
        )
        timing_data["parallel_processing"] = time.time() - parallel_start

        # 6. 达人风格解析结果验证（仅保留这1个验证，删除另外2个）
        default_creator_style = {
            "style_type": request.direction,
            "style_analysis": "使用默认风格（因达人风格分析失败）",
            "content_suggestions": "突出产品核心卖点，语言简洁明了"
        }

        # 仅验证达人风格分析结果
        if isinstance(creator_style, dict):
            if "error" in creator_style or "raw_content" in creator_style:
                error_msg = creator_style.get("error", "未知错误")
                logger.warning(f"达人风格分析失败，使用默认值: {error_msg}")
                creator_style = default_creator_style
        elif isinstance(creator_style, str) and creator_style.startswith("处理失败"):
            logger.warning(f"达人风格分析失败，使用默认值: {creator_style}")
            creator_style = default_creator_style

        # 提取风格类型（保留原逻辑，用于后续二重判断）
        try:
            extracted_style_type = creator_style.get("style_type", request.direction) if isinstance(creator_style,
                                                                                                    dict) else \
                json.loads(creator_style).get("style_type", request.direction)
        except (json.JSONDecodeError, TypeError):
            extracted_style_type = request.direction
        logger.info(f"提取到的风格类型: {extracted_style_type}")

        # 7. 提取final方向（用于处理器入参）
        final_direction = extract_direction_from_content(final_strategy)
        logger.info(f"提取的最终内容方向: {final_direction or '未识别'}")

        # 8. 二重判断：完全保留原逻辑（按风格类型进入seeding/evaluation）
        processor_start = time.time()
        style_type = extracted_style_type  # 用于二重判断的风格类型（不改动）
        expert_style = creator_style  # 达人风格解析输出（中间产物）
        final_result = None  # 处理器输出（Group1）
        shot_list = []

        style_type_lower = request.direction.lower()  # style_type就是request.direction

        if "种草" in style_type_lower:
            logger.debug("进入种草类处理器（seeding）")
            # 按需求调整seeding入参：仅保留6个指定参数
            seeding_inputs = {
                "product_name": request.brand_name,  # 产品名（请求体）
                "ProductHighlights": product_highlights,  # 产品亮点（请求体）
                "notice": request.notice,  # 注意事项（请求体）
                "outline": video_outline,  # 视频大纲（从飞书表格读取）
                "expert_style": expert_style,  # 达人风格解析输出（中间产物）
                "outline_advice": outline_advice,  # 大纲方向建议（请求体）
                "direction": final_direction
            }
            processor = create_processor("seeding", volcano_client, request.notice or "")
            shot_list = await processor.process(seeding_inputs)  # 直接接收分镜列表
            logger.info(f"种草类获取分镜数据: {len(shot_list) if isinstance(shot_list, list) else 0} 个镜头")


        elif "测评" in style_type_lower:
            logger.debug("进入测评类处理器（evaluation）")
            # 构建入参（与之前跑通代码的参数名对齐，仅调整字段名适配当前变量）
            evaluation_inputs = {
                "product_name": request.brand_name,
                "ProductHighlights": product_highlights,
                "notice": request.notice,
                "outline": video_outline,  # 视频大纲（从飞书表格读取）
                "expert_style": expert_style,
                "outline_advice": outline_advice,
                "direction": final_direction
            }
            processor = create_processor("evaluation", volcano_client, request.notice or "")
            # 核心：直接获取处理器返回结果作为shot_list（不做任何转换，和之前跑通逻辑一致）
            shot_list = await processor.process(evaluation_inputs)
            # 仅做日志验证（无多余逻辑）
            logger.info(f"获取分镜数据: {len(shot_list) if isinstance(shot_list, list) else 0} 个镜头")

        timing_data["processor_processing"] = time.time() - processor_start

        # 9. 视频脚本生成：按需求调整入参（仅4个：expert_style+ProductHighlights+Group1+direction）
        script_start = time.time()
        video_script = await process_video_script(
            expert_style=expert_style,  # 达人风格解析输出
            ProductHighlights=product_highlights,  # 产品亮点（请求体）
            Group1=final_result,  # 处理器输出（seeding/evaluation）
            direction=final_direction or request.direction,  # final方向或默认
            volcano_client=volcano_client
        )
        timing_data["video_script_processing"] = time.time() - script_start

        # 10. 写入飞书表格（新增关闭客户端逻辑）
        sheet_start = time.time()
        spreadsheet_util = FeishuSpreadsheetUtil()
        try:
            strategy_result_str = json.dumps(final_result, ensure_ascii=False) if isinstance(final_result,
                                                                                             dict) else str(
                final_result)
            logger.info(f"视频脚本结构: {type(video_script)}")
            logger.info(f"处理器输出（Group1）长度: {len(strategy_result_str)}字符")

            # 调用写入方法 - 使用从video_outline_link提取的token
            sheet_result = await spreadsheet_util.write_to_existing_sheet(
                spreadsheet_token=spreadsheet_token,  # 使用从video_outline_link提取的token
                video_script=video_script,
                strategy_result=strategy_result_str,
                shot_list=shot_list
            )
            timing_data["feishu_processing"] = time.time() - sheet_start
        except Exception as e:
            logger.error(f"飞书处理失败: {str(e)}", exc_info=True)
            sheet_result = {"status": "error", "message": str(e)}
            timing_data["feishu_processing"] = time.time() - sheet_start
        finally:
            # 关闭异步客户端，避免资源泄漏
            await spreadsheet_util.sheet_manager.close()

        # 11. 时间可视化+返回结果（保留原逻辑，返回时加上token）
        timing_visual = generate_timing_visualization(timing_data)
        logger.info(f"\n{timing_visual}\n")
        total_time = time.time() - start_time

        if sheet_result.get("status") == "success":
            return {
                "status": "success",
                "spreadsheet_url": sheet_result.get("spreadsheet_url"),
                "message": "内容策略已生成并保存到飞书表格",
                "style_type": style_type,
                "final_direction": final_direction,
                "resource_type": "online",
                "sheet_token": spreadsheet_token,  # 返回从video_outline_link提取的token
                "timing": {
                    "total_seconds": round(total_time, 2),
                    "breakdown": timing_data,
                    "visualization": timing_visual
                }
            }
        else:
            return {
                "status": "error",
                "message": f"内容策略生成成功，但飞书保存失败: {sheet_result.get('message')}",
                "final_strategy": final_result,
                "video_script": video_script,
                "resource_type": "online",
                "sheet_token": spreadsheet_token,  # 返回从video_outline_link提取的token
                "timing": timing_data
            }

    except Exception as e:
        logger.error(f"处理请求时出错: {str(e)}", exc_info=True)
        return {
            "status": "error",
            "message": f"处理请求时出错: {str(e)}"
        }


# 以下为核心处理函数（仅保留需要的，删除另外2个）
async def process_creator_style(caption, image, direction, volcano_client):
    """达人风格解析：输出expert_style（中间产物）"""
    logger.info(f"处理达人风格解析：文字长度={len(caption)}，Base64图片数={len(image)}，风格={direction}")
    user_prompt = PROMPT_CREATOR_STYLE_USER.format(
        caption=caption,
        image=f"共{len(image)}张图片" if image else "无图片",
        style=direction
    )
    result = await volcano_client.call_volcano_api(
        PROMPT_CREATOR_STYLE_SYSTEM,
        user_prompt,
        image
    )
    return extract_json_from_text(result)


async def process_final_content(direction, outline_advice, volcano_client):
    """final策略生成：仅输入direction+outline_advice（按需求仅2个入参）"""
    logger.info(f"生成final策略：direction={direction}，outline_advice={outline_advice or '无'}")
    user_prompt = PROMPT_FINAL_CONTENT_USER.format(
        direction=direction,  # 仅保留这2个入参
        outline_advice=outline_advice or "无特殊大纲方向建议，需突出产品核心卖点"
    )
    result = await volcano_client.call_volcano_api(
        PROMPT_FINAL_CONTENT_SYSTEM,
        user_prompt
    )
    return extract_json_from_text(result)


async def process_video_script(expert_style, ProductHighlights, Group1, direction, volcano_client):
    logger.info(f"生成视频脚本：expert_style类型={type(expert_style)}，ProductHighlights长度={len(ProductHighlights)}")
    expert_style_str = json.dumps(expert_style, ensure_ascii=False)
    Group1_str = json.dumps(Group1, ensure_ascii=False) if isinstance(Group1, (dict, list)) else str(Group1)

    user_prompt = PROMPT_VIDEO_SCRIPT_USER.format(
        expert_style=expert_style_str,
        ProductHighlights=ProductHighlights,
        Group1=Group1_str,
        direction=direction
    )
    result = await volcano_client.call_volcano_api(
        PROMPT_VIDEO_SCRIPT_SYSTEM,
        user_prompt
    )

    # 使用从feishu_spreadsheet导入的clean_json_string函数
    try:
        # 步骤1：先清理模型返回的字符串（关键！示例代码的核心逻辑）
        cleaned_result = clean_json_string(result.strip())  # 直接使用导入的函数
        # 步骤2：解析清理后的JSON
        model_json = json.loads(cleaned_result)
        # 步骤3：提取text和label（忽略title）
        video_script = {
            "text": model_json.get("text", ""),  # 只取text
            "label": model_json.get("label", "")  # 只取label
        }
        logger.info(f"成功解析视频脚本：text长度={len(video_script['text'])}, label={video_script['label']}")
        return video_script

    except json.JSONDecodeError as e:
        logger.error(f"模型返回JSON解析失败（已清理）：{str(e)}，清理后结果：{cleaned_result[:100]}")
        # 降级处理：尝试从原始字符串中提取text（避免B9写入完整JSON）
        return {
            "text": re.sub(r'["{}]', '', result.strip())[:500],  # 去除JSON符号
            "label": "解析失败（JSON格式错误）"
        }
    except Exception as e:
        logger.error(f"视频脚本处理异常：{str(e)}")
        return {
            "text": "",
            "label": "处理失败"
        }